package by.alex.dzeshko.facebooksuperclient.image;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.http.client.ClientProtocolException;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.support.v4.util.LruCache;
import android.text.TextUtils;
import android.util.Log;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import by.alex.dzeshko.facebooksuperclient.ContextHolder;
import by.alex.dzeshko.facebooksuperclient.R;
import by.alex.dzeshko.facebooksuperclient.async.CustomExecutorAsyncTask;
import by.alex.dzeshko.facebooksuperclient.async.common.ParamCallback;
import by.alex.dzeshko.facebooksuperclient.http.HttpManager;
import by.alex.dzeshko.facebooksuperclient.utils.Md5;

public class ImageLoader {
	
	private static final String LOG_TAG = "ImageLoader2";

	private static ImageLoader instance;
	
	public static ImageLoader getInstance() {
		if (instance == null) {
			instance = new ImageLoader();
		}
		return instance;
	}
	
	Context context = ContextHolder.getInstance().getContext();
	
	final LruCache<String, Bitmap> mMemoryCache;
	
	private List<Callback> mQueue;
	
	private int mNumberOnExecute;
	
	public interface Callback {
		
		String getUrl();
		
		void onSuccess(Bitmap bm);

		void onError(Exception e);
	}
	
	private static final int CORE_POOL_SIZE = 1;
	
	private static final int MAXIMUM_POOL_SIZE = 50;
	
	private static final int KEEP_ALIVE = 10;

	private static final BlockingQueue<Runnable> sWorkQueue =
	            new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);

	private static final ThreadFactory sThreadFactory = new ThreadFactory() {
	        private final AtomicInteger mCount = new AtomicInteger(1);

	        public Thread newThread(Runnable r) {
	            return new Thread(r, "ImageAsyncTask #" + mCount.getAndIncrement());
	        }
	    };

	private static final ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
	            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
	
	private ImageLoader() {
		
		final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
		final int cacheSize = 1024 * 1024 * memClass / 4;
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
	        @Override
	        protected int sizeOf(String key, Bitmap bitmap) {
	            return bitmap.getWidth()*bitmap.getHeight()*4;
	        }
	        
	        @Override
			protected void entryRemoved(boolean evicted, String key,
					Bitmap oldValue, Bitmap newValue) {
				if (evicted){
					Log.d("evicted","evicted");
					oldValue.recycle();
					System.gc();
					oldValue = null;
				}
			}
	    };		
	    mQueue = Collections.synchronizedList(new ArrayList<Callback>());
	    mNumberOnExecute = 0;
	}
	
	public void bind(final BaseAdapter adapter, final ImageView imageView, final String url) {
		
		Bitmap bitmap = null;
		bitmap = mMemoryCache.get(url);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		} else {
				mQueue.add(0, new Callback() {
					
					@Override
					public void onSuccess(Bitmap bm) {
						imageView.setImageBitmap(bm);
						adapter.notifyDataSetChanged();
					}
					
					@Override
					public String getUrl() {
						return url;
					}
	
					@Override
					public void onError(Exception e) {
						Log.e(LOG_TAG, e.toString());
					}
				});
			
		}
		proceed();
	}
	
	public void bind(final ImageView imageView, final String url, final ParamCallback<Void> paramCallback) {
		
		Bitmap bitmap = null;
		bitmap = mMemoryCache.get(url);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			paramCallback.onSuccess(null);
			return;
		} else {
				mQueue.add(0, new Callback() {
					
					@Override
					public void onSuccess(Bitmap bm) {
						imageView.setImageBitmap(bm);
						paramCallback.onSuccess(null);
					}
					
					@Override
					public String getUrl() {
						return url;
					}
	
					@Override
					public void onError(Exception e) {
						imageView.setImageResource(android.R.drawable.stat_notify_error);
						paramCallback.onError(e);
						Log.e(LOG_TAG,"result - error:"+e.getMessage());
					}
				});
		}
		proceed();
	}
	public void bind(final ImageView imageView, final String url) {
		
		Bitmap bitmap = null;
		bitmap = mMemoryCache.get(url);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		} else {
				mQueue.add(0, new Callback() {
					
					@Override
					public void onSuccess(Bitmap bm) {
						imageView.setImageBitmap(bm);
					}
					
					@Override
					public String getUrl() {
						return url;
					}
	
					@Override
					public void onError(Exception e) {
						imageView.setImageResource(android.R.drawable.stat_notify_error);
						Log.e(LOG_TAG, e.toString());
					}
				});
		}
		proceed();
	}

	private void proceed() {
		if(mNumberOnExecute>30) {
			if(mQueue.size()>2)
				mQueue.remove(mQueue.size()-1);
			return;
		}
		if (mQueue.isEmpty()) {
			return;
		}
		final Callback callback = mQueue.remove(0);
		
		new CustomExecutorAsyncTask<Callback, Void, Object>() {
			
			@Override
			protected void onPreExecute() {
				mNumberOnExecute++;
			}

			@Override
			protected Object doInBackground(Callback... params) {
				
				String url = params[0].getUrl();
				if(TextUtils.isEmpty(url))
					return new IllegalArgumentException();
				Bitmap bitmap = null;
				String md5_url = Md5.md5(url);
				File cacheDir = context.getCacheDir();
				File cacheFile = new File(cacheDir, md5_url);
				FileInputStream fis = null;
				if(cacheFile.exists()){
					try{
						fis = new FileInputStream(cacheFile);
						bitmap = BitmapFactory.decodeStream(fis);
					} catch(IOException e){
						Log.e(LOG_TAG,"decode exc: "+e);
					} finally {
						if(fis!=null){
							try {
								fis.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
					if(bitmap!=null) return bitmap;
					else {
						bitmap = loadBitmap(url);
						return bitmap;
					}
				} else{
					bitmap = loadBitmap(url);
					return bitmap;
				}
			}

			private Bitmap loadBitmap(String url) {
				Bitmap bitmap = null;
				
				try {
					bitmap = HttpManager.getInstance().loadBitmap(url);
				} catch (ClientProtocolException e) {
					onPostExecute(e);
					e.printStackTrace();
				} catch (IOException e) {
					onPostExecute(e);
					e.printStackTrace();
				}
				
				if(bitmap!=null)
					putBitmapToDiscCache(bitmap, url);
				return bitmap;
			}

			@Override
			protected void onPostExecute(Object result) {
				if (result instanceof Bitmap) {
					Bitmap bitmap = (Bitmap)result;
					mMemoryCache.put(callback.getUrl(), bitmap);
					callback.onSuccess(bitmap);
					
				} else if(result instanceof Exception) {
					Log.e(LOG_TAG, result.toString());
					callback.onError((Exception)result);
				}
				mNumberOnExecute--;
			}
			
		}.execute(mExecutor, callback);
		
	}
	
	protected void putBitmapToDiscCache(Bitmap bitmap, String url){
		
		File cacheDir = context.getCacheDir();
		File cacheFile = new File(cacheDir, Md5.md5(url));
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(cacheFile);
			bitmap.compress(CompressFormat.JPEG, 100, fos);
			fos.flush();
		} catch (FileNotFoundException e) {
			Log.e("ImLoader.PutToDisc",e.toString());
			e.printStackTrace();
		} catch (IOException e) {
			Log.e("ImLoader.PutToDisc",e.toString());
			e.printStackTrace();
		} finally{
			if(fos!=null){
				try {
					fos.close();
				} catch (IOException e) {
					// ignored closed 
				}
			}
		}
	}



}
